
#include <BStyle>
#include <BLabel>
#include <BButton>
#include <BVBoxLayout>
#include <BHBoxLayout>
#include <BMessageBox>
#include <BFileDialog>

#include "MethodFormat.h"
#include "CheckDialog.h"

CheckDialog::CheckDialog(BWidget* parent) : BDialog(parent)
{
	this->setTitle("Check Difference");

	BHBoxLayout* splitLayout = new BHBoxLayout(this);
	splitLayout->setSplitable(true);
	splitLayout->setSpacing(5);
	{
		_treeWidget = new BTreeWidget("Header Contents");
		_treeWidget->setSpreadSize(100);
		_treeWidget->setTitleVisible(true);

		BWidget* rightWidget = new BWidget("right");
		rightWidget->setWidthPolicy(Policy_Dynamic);
		{
			_titleLabel = new BLabel();
			_titleLabel->setBorder(1);

			_attribEdit = new BTextEdit("Attributes");
			_attribEdit->setReadOnly(true);
			_attribEdit->setScrollHint(ScrollHint_Floating);

			_textEdit = new BTextEdit("Plain text");
			_textEdit->setReadOnly(true);
			_textEdit->setScrollHint(ScrollHint_Floating);

			BVBoxLayout* vlayout = new BVBoxLayout(rightWidget);
			vlayout->addWidget(_titleLabel, 0);
			vlayout->addWidget(_attribEdit, 1);
			vlayout->addWidget(_textEdit, 5);
		}

		splitLayout->addWidget(_treeWidget, 1);
		splitLayout->addWidget(rightWidget, 1);
	}

	BHBoxLayout* buttonLayout = new BHBoxLayout(this, Part_Bottom);
	buttonLayout->setMargin(Part_Bottom, 2);
	{
		_saveAsButton = new BButton("save-as");
		_saveButton = new BButton("save");
		_cancelButton = new BButton("cancel");

		buttonLayout->addWidget(_saveAsButton);
		buttonLayout->addSpring(10);
		buttonLayout->addWidget(_saveButton);
		buttonLayout->addWidget(_cancelButton);
	}

	connect(_treeWidget, Signal_ItemSelected, &CheckDialog::slotTreeItemSelected);
	connect(_saveAsButton, Signal_Clicked, &CheckDialog::slotSaveAsButtonClicked);
	connect(_saveButton, Signal_Clicked, &CheckDialog::slotSaveButtonClicked);
	connect(_cancelButton, Signal_Clicked, &CheckDialog::slotCancelButtonClicked);
}
CheckDialog::~CheckDialog()
{

}

bool CheckDialog::openHeadFile(const BString& fileName)
{
	_fileName = fileName;

	BFile file(fileName);
	if (!file.open(IO_Read))
		return false;

	_headRoot = new BXmlNode("html");
	_headRoot->setFormat("<>\n</");
	BXmlNode* headNode = _headRoot->addChild("head");
	headNode->setFormat("<>\n</>\n");
	{
		BXmlNode* metaNode = headNode->addChild("meta");
		metaNode->setAttrib("charset", "utf-8");
		metaNode->setFormat("</>\n");

		BXmlNode* titleNode = headNode->addChild("title");
		titleNode->setText(fileName.name());
		titleNode->setFormat("</>\n");

		BXmlNode* linkNode = headNode->addChild("link");
		linkNode->setAttrib("rel", "stylesheet");
		linkNode->setAttrib("type", "text/css");
		linkNode->setAttrib("href", "./style.css");
	}

	BXmlNode* body = _headRoot->addChild("body");
	body->setFormat("<>\n</>\n");
	BString text;
	file.read(text);

	int pos = 0;
	BString word;
	BStringArray words;
	while (pos < text.size())
	{
		pos = text.word(word, pos);
		if (word == "template")
		{
			pos = text.find('\n', pos) + 1;
			continue;
		}
		if (word == "enum")
		{
			BXmlNode* divNode = body->addChild("div");
			divNode->setFormat("<>\n</>\n");

			pos = text.word(word, pos);
			BXmlNode* enumTitle = divNode->addChild("h3");
			enumTitle->setText(word);

			BXmlNode* enumTable = readEnum(text, pos);
			divNode->addChild(enumTable);

			continue;
		}
		if (word == "class")
		{
			BXmlNode* classNode = readClass(text, pos);
			body->addChild(classNode);
			continue;
		}
	}

	_treeWidget->clearItems();

	for (int i = 0; i < body->childCount(); i++)
	{
		BTreeItem* item = createTreeItem(body->child(i));
		_treeWidget->addItem(item);
	}

	return true;
}
bool CheckDialog::loadHtmlFile(const BString& htmlFileName)
{
	_htmlFileName = htmlFileName;
	BFile file(_htmlFileName);
	if (!file.open(IO_Read))
		return false;

	BXmlDocument document;
	if (!document.read(file))
		return false;

	BXmlNode* htmlRoot = document.node("html");
	if (!htmlRoot)
		return false;

	const BStyle* style = this->realStyle();

	for (int i = 0; i < _treeWidget->itemCount(); i++)
	{
		BTreeItem* topItem = _treeWidget->item(i);

		BTreeItem* resumeItem = topItem->child(applyLanguage("Resume"));
		if (resumeItem)
		{
			BXmlNode* resumeNode = resumeItem->asset();
			BXmlNode* htmlResumeNode = htmlRoot->find("p:id=\"resume\"");
			if (resumeNode && htmlResumeNode)
			{
				resumeNode->copy(htmlResumeNode);
				BXmlNode* descNode = resumeNode->child("a:href=#desc");
				if (!descNode)
				{
					descNode = resumeNode->addChild('a');
					descNode->setAttrib("href", "#desc");
				}
				descNode->setText(applyLanguage("desc"));
				resumeNode->setText(htmlResumeNode->text());
			}
		}

		BTreeItem* descItem = topItem->child(applyLanguage("Description"));
		if (descItem)
		{
			BXmlNode* descNode = descItem->asset();
			BXmlNode* htmlDescNode = htmlRoot->find("div:id=\"desc\"");
			if (descItem && descNode && htmlDescNode)
			{
				for (int i = 0; i < htmlDescNode->childCount(); i++)
				{
					BXmlNode* child = htmlDescNode->child(i);
					if (child->name() != "h2")
					{
						descNode->addChild(child);
						i--;
					}
				}
			}
		}

		for (int c = 0; c < topItem->childCount(); c++)
		{
			BTreeItem* child = topItem->child(c);
			if (child->text() != "public" && child->text() != "protected")
				continue;
			BString desc;
			for (int m = 0; m < child->childCount(); m++)
			{
				BTreeItem* methodItem = child->child(m);
				BXmlNode* descNode = methodItem->asset();

				desc.reset();
				desc << "div:id=\"" << descNode->attrib("id") << '\"';
				BXmlNode* node = htmlRoot->find(desc);
				if (!node && descNode->attrib("id").endWith(" const"))
				{
					BString idstr = descNode->attrib("id") - " const";
					desc.reset();
					desc << "div:id=\"" << idstr << '\"';
					node = htmlRoot->find(desc);
				}
				if (node)
				{
					methodItem->setIcon(style->icon("normal"));
					for (int i = 0; i < node->childCount(); i++)
					{
						BXmlNode* child = node->child(i);
						if (child->name() == "h3")
							continue;
						if (child->name() == "table" && child->attrib("class") == "enum")
						{
							BXmlNode* table = descNode->child("table:class=enum");
							for (int r = 0; r < table->childCount(); r++)
							{
								BXmlNode* tr = table->child(r);
								BString keyText = tr->child(0)->text();
								keyText.prepend("td@");
								if (BXmlNode* keyNode = child->find(keyText))
								{
									tr->removeChild(1);
									tr->insertChild(1, keyNode->next());
								}
							}
						}
						else
						{
							descNode->addChild(child);
							i--;
						}
					}
				}
				else
				{
					methodItem->setIcon(style->icon("new"));
				}
			}
		}
	}
	return true;
}

bool CheckDialog::saveHtmlFile()
{
	BFile file(_htmlFileName);
	if (!file.open(IO_Write))
		return false;

	BXmlDocument document;
	bool ret = document.write(file, _headRoot);
	if (!ret)
		return false;

	if (this->title().contain('*'))
	{
		BString title = this->title();
		title.remove('*');
		this->setTitle(title);
	}
	return true;
}

BString CheckDialog::applyLanguage(const char* text)
{
	const BStyle* style = this->realStyle();
	BString result;
	style->translate(text, result);
	if (result.empty())
		return text;
	return result;
}
int CheckDialog::strideBlock(const BString& text, int pos)
{
	int depth = 0;
	int level = 0;
	while (pos < text.size())
	{
		if (text[pos] == '{')
		{
			level++;
			depth++;
		}
		if (text[pos] == '}')
			level--;
		if (text[pos] == '\n' && depth > 0 && level == 0)
			return pos + 1;
		pos++;
	}
	return pos;
}

BXmlNode* CheckDialog::readEnum(const BString& text, int& pos)
{
	BString str;
	text.line(str, pos);
	str.remove("enum");
	str.trimmed();

	BXmlNode* tableNode = new BXmlNode("table");
	tableNode->setFormat("\n<\n</");
	tableNode->setAttrib("class", "enum");

	pos = text.find('{', pos);
	pos++;
	int stop = text.find('}', pos);
	int value = 0;
	str.reset();
	while (pos <= stop)
	{
		char ch = text[pos];
		if (ch == ',' || ch == '}')
		{
			str.trimmed();
			BXmlNode* tr = tableNode->addChild("tr");
			tr->setFormat("\n<");
			BXmlNode* td0 = tr->addChild("td");
			BXmlNode* td1 = tr->addChild("td", "desc");
			if (str.contain('='))
			{
				BStringArray words;
				if (2 == str.split(words, '='))
				{
					words[0].trimmed();
					td0->setText(words[0]);
				}
			}
			else
			{
				td0->setText(str);
			}
			str.reset();
		}
		else
		{
			str.append(ch);
		}
		pos++;
	}
	pos = text.line(str, pos);

	return tableNode;
}
BXmlNode* CheckDialog::readClass(const BString& text, int& pos)
{
	BString line;
	pos = text.line(line, pos);
	line.trimmed();
	if (line.endWith(';'))
		return 0;

	line.remove("class ");
	line.remove("BWEAPI ");
	BString name = line.word(0);

	BXmlNode* classNode = new BXmlNode("div");
	classNode->setFormat("\n<>\n</");
	classNode->setAttrib("class", "class");
	classNode->setAttrib("name", name);

	BXmlNode* class_name_node = new BXmlNode("h1");
	class_name_node->setText(name);
	class_name_node->setFormat("/>\n");
	classNode->addChild(class_name_node);

	int inherits_pos = text.find(" : public ");
	BString class_inherits = text.word(inherits_pos + 10);
	if (class_inherits.size())
	{
		BXmlNode* inherits_node = classNode->addChild("p");
		inherits_node->setText(applyLanguage("Inherites from"));
		inherits_node->setFormat("/>\n");

		BXmlNode* linkNode = inherits_node->addChild("a");
		linkNode->setText(class_inherits);
		class_inherits.append(".html");
		linkNode->setAttrib("href", class_inherits);
	}

	BXmlNode* resumeNode = classNode->addChild("p");
	resumeNode->setAttrib("id", "resume");
	resumeNode->setFormat("</>\n");

	BXmlNodeHolderArray tableNodes;
	while (pos < text.size())
	{
		pos = text.line(line, pos);
		line.trimmed();
		line -= ':';

		if (line == "public" || line == "protected")
		{
			BXmlNodeHolder node = collectMembers(text, pos);
			if (node->childCount() == 0)
				continue;

			BXmlNode* titleNode = new BXmlNode("h2");
			titleNode->setText(line);
			titleNode->setFormat("/>\n");
			classNode->addChild(titleNode);

			node->setFormat(">\n</>\n");
			node->setAttrib("id", line);
			classNode->addChild(node);

			if (node->name() == "table")
				tableNodes.append(node);
		}
	}

	BXmlNode* descNode = classNode->addChild("div");
	descNode->setAttrib("id", "desc");
	descNode->setFormat("<>\n</>\n");
	{
		BXmlNode* desc_title = descNode->addChild("h2");
		desc_title->setText(applyLanguage("Detail Description :"));
		desc_title->setFormat("/>\n");
	}

	BXmlNode* docsNode = classNode->addChild("h2");
	docsNode->setAttrib("id", "docs");
	docsNode->setText(applyLanguage("Member Documentation :"));
	docsNode->setFormat("</>\n");

	for (int i = 0; i < tableNodes.size(); i++)
	{
		BXmlNode* tableNode0= tableNodes[i];
		for (int i = 0; i < tableNode0->childCount(); i++)
		{
			BXmlNode* rowNode = tableNode0->child(i);
			BXmlNode* linkNode = rowNode->find("a");
			if (!linkNode)
				continue;
			const char* idStr = linkNode->attrib("href").cstr() + 1;

			BXmlNode* divNode = classNode->addChild("div");
			divNode->setAttrib("id", idStr);
			divNode->setFormat("<>\n</>\n");

			BXmlNode* titleNode = divNode->addChild("h3");
			titleNode->setFormat("/>\n");

			BXmlNode* table = linkNode->child("table");
			if (table)
				divNode->addChild(table);

			titleNode->setText(rowNode->plainText());
		}
	}

	return classNode;
}
BXmlNode* CheckDialog::collectMembers(const BString& text, int& pos)
{
	BXmlNode* table = new BXmlNode("table");
	BString line;
	BString href;
	BString name;
	BString params;
	while (pos < text.size())
	{
		int end = text.line(line, pos);
		if (line.contain("public:") || line.contain("protected:") || line.contain("private:"))
			break;
		if (line.contain("};"))
			break;
		pos = end;
		if (line.contain("template"))
			continue;
		if (line.contain("typedef"))
			continue;
		if (line.contain("//"))
		{
			line.trimmed();
			if (line.beginWith("//"))
				continue;
			continue;
		}
		if (line.contain("enum"))
		{
			line.trimmed();

			BXmlNode* divNode = table->addChild("div");
			divNode->setFormat("<>\n</>\n");

			BXmlNode* enumTitle = divNode->addChild("h3");
			enumTitle->setText(line);

			BXmlNode* enumTable = readEnum(text, pos);
			divNode->addChild(enumTable);

			return divNode;
		}
		if (line.contain("class"))
		{
			line.remove("class");
			line.trimmed();

			BXmlNode* rowNode = table->addChild("tr");
			rowNode->setFormat("/>\n");

			BXmlNode* td0 = rowNode->addChild("td");
			td0->setAttrib("align", "right");
			td0->setText("class ");

			BXmlNode* td1 = rowNode->addChild("td");
			BXmlNode* link = td1->addChild("a");
			link->setAttrib("href", BString('#') + line);
			link->setText(line);

			pos = strideBlock(text, pos);

			continue;
		}
		line.simplify();
		line.trimmed();
		if (line.endWith(')'))
		{
			pos = strideBlock(text, pos);
			line.append(';');
		}
		bool cnst = false;
		if (line.endWith("const"))
		{
			pos = strideBlock(text, pos);
			line.append(';');
			cnst = true;
		}
		if (line.endWith("const;"))
		{
			cnst = true;
		}
		if (line.empty())
			continue;

		int name_end = line.find('(');
		int name_begin = line.find("operator");
		if (name_begin == line.size())
			name_begin = name_end - 1;
		while (name_begin > 0)
		{
			if (isspace(line[name_begin]))
			{
				name_begin++;
				break;
			}
			name_begin--;
		}
		line.substr(name, name_begin, name_end);

		int param_begin = name_end + 1;
		int param_end = line.rfind(')');
		line.substr(params, param_begin, param_end);
		if (params.size())
		{
			if (params.contain(','))
			{
				BStringArray words = params.split(',');
				params.reset();
				for (int i = 0; i < words.size(); i++)
				{
					BString& word = words[i];
					int equal_pos = word.find('=');
					if (equal_pos < word.size())
					{
						word.remove(equal_pos, word.size());
						word.trimmed();
					}
					int space_pos = word.rfind(' ');
					word.remove(space_pos, word.size());
					params.append(word);
					params.append(',');
				}
				params -= ',';
			}
			else
			{
				int equal_pos = params.find('=');
				if (equal_pos < params.size())
				{
					params.remove(equal_pos, params.size());
					params.trimmed();
				}
				int space_pos = params.rfind(' ');
				params.remove(space_pos, params.size());
			}
		}

		BXmlNode* rowNode = table->addChild("tr");
		rowNode->setFormat("/>\n");
		{
			BXmlNode* node0 = rowNode->addChild("td");
			node0->setAttrib("align", "right");
			node0->setText(line.substr(0, name_begin));

			BXmlNode* node1 = rowNode->addChild("td");
			href.reset();
			href << '#' << name << '(' << params << ')';
			if (cnst)
				href << " const";
			BXmlNode* method_link = node1->addChild("a");
			method_link->setText(name);
			method_link->setAttrib("href", href);
			BString text = line.substr(name_end);
			node1->addText(text);
		}
	}
	return table;
}

BTreeItem* CheckDialog::createTreeItem(BXmlNode* node)
{
	const BStyle* style = this->realStyle();

	if (node->attrib("class") == "class")
	{
		BTreeItem* item = new BTreeItem(node->attrib("name"));
		item->setIcon(style->icon("class"));

		BTreeItem* resumeItem = item->addChild(applyLanguage("Resume"));
		resumeItem->setAsset(node->find(":id=resume"));
		resumeItem->setIcon(style->icon("resume"));

		BTreeItem* descItem = item->addChild(applyLanguage("Description"));
		descItem->setAsset(node->find(":id=desc"));
		descItem->setIcon(style->icon("desc"));

		for (int i = 0; i < node->childCount(); i++)
		{
			BTreeItem* child = createTreeItem(node->child(i));
			item->addChild(child);
		}
		return item;
	}

	if (node->name() == "table")
	{
		BXmlNode* classNode = node->parent();
		const BString& table_id = node->attrib("id");
		BTreeItem* item = _treeWidget->addItem(table_id);
		item->setIcon(style->icon(table_id));
		for (int i = 0; i < node->childCount(); i++)
		{
			BXmlNode* rowNode = node->child(i);
			BXmlNode* linkNode = rowNode->find("a");
			if (!linkNode)
				continue;
			BString desc = linkNode->attrib("href");
			desc.replace('#', "div:id=\"");
			desc << '\"';
			BXmlNode* divNode = classNode->find(desc);
			BTreeItem* methodItem = item->addChild(rowNode->plainText());
			methodItem->setAsset(divNode);
			methodItem->setTextFormat(new MethodFormat);
			methodItem->setIcon(style->icon("new"));
		}
		return item;
	}

	return 0;
}

void CheckDialog::slotTreeItemSelected(BObject* object, const BValue& value)
{
	if (BTreeItem* item = value)
	{
		if (BXmlNode* node = item->asset())
		{
			BString attribText;
			const BMap<BString, BString>& attribMap = node->attribMap();
			for (auto it = attribMap.begin(); it.valid(); ++it)
			{
				attribText << it.key() << '=' << it.value() << '\n';
			}
			_attribEdit->setText(attribText);
			BString title;
			BString text;
			for (int i = 0; i < node->childCount(); i++)
			{
				BXmlNode* child = node->child(i);
				if (child->name().contain('h'))
				{
					title.append(child->plainText());
					continue;
				}
				text.append(child->plainText());
				text.append('\n');
			}
			_titleLabel->setText(title);
			_textEdit->setText(text);
		}
		else
		{
			_titleLabel->setText(BString());
			_attribEdit->setText(BString());
			_textEdit->setText(BString());
		}
	}
}
void CheckDialog::slotTextEditFinished(BObject* object, const BValue& value)
{
	if (BTreeItem* item = _treeWidget->selectedItem())
	{

	}
}

void CheckDialog::slotSaveAsButtonClicked()
{
	const BString& fileName = BFileDialog::SaveFile("Save-As", "", "*.html");
	if (fileName.size())
	{
		BXmlDocument document;
		document.save(fileName, _headRoot);
	}
}
void CheckDialog::slotSaveButtonClicked()
{
	if (this->saveHtmlFile())
	{
		this->close(true);
		return;
	}
	BMessageBox::Warning(this, "Warning", "Save html file failed.");
	this->close();
}
void CheckDialog::slotCancelButtonClicked()
{
	this->close();
}

void CheckDialog::updateEvent(const BEvent& event)
{

}

